Migrating from Next.js to Catalyst
Step 1: Project Setup
Create a new Catalyst project:
Perform the entire migration in a new folder to avoid dependency conflicts between Next.js and Catalyst.
Step 2: Project Structure
Understand the differences in project structure:
Next.js:
pages/
public/
styles/Catalyst:
config/ # Configuration keys
src/ # Application source code
components/
routes/
services/
utils/
client/ # Client app entry point
server/ # API and middleware
build/ # Bundled code📚 For detailed information about Catalyst's folder structure, see: Folder Structure Guide
Move your existing components from various locations to
src/components/
.Relocate any utility functions to
src/utils/
.
Step 3: Routing
Replace Next.js file-based routing with Catalyst's route configuration.
Open src/routes/index.ts.
Define your routes using the
react-router-v6
style:const routes = [
{
path: "/",
index: true,
component: HomePage,
},
{
path: "/cart",
component: CartComponent,
children: [
{
path: "",
component: CartChild
},
{
path: "prescription",
component: Prescription
}
],
},
{
path: "/health-products",
component: HealthProducts,
},
]Update dynamic routes to use react-router-v6 syntax:
[id].js
→path: "/products/:id"
[...slug].js
→path: "/products/*"
Step 4: Migration of Folder Structure
Move your page components from
pages/
to act as route components in the routes definition.Ensure all other components are in
src/components/
.Move your API routes from
pages/api/
to theserver/
directory in Catalyst.
Step 5: Next.js App Router to Catalyst Migration
If you're migrating from Next.js App Router (with React Server Components), here's how to adapt your components:
Key Differences:
Next.js RSC:
- Components can be async and fetch data directly
- Server Components render on the server only
- Client Components require 'use client' directive
Catalyst Framework:
- Traditional React components with separate data fetchers
serverFetcher
: Runs during SSR, server-only codeclientFetcher
: Runs during client-side navigation- Data accessed via
useCurrentRouteData
hook
Migration Example: Basic Data Fetching
Next.js RSC Implementation:
// app/products/page.tsx
async function ProductsPage() {
const products = await fetch('https://api.example.com/products').then(r => r.json());
return (
<div>
<h1>Products</h1>
{products.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}
export default ProductsPage;
Catalyst Implementation:
// pages/Products.jsx
import { useCurrentRouteData } from "@tata1mg/router";
const ProductsPage = () => {
const { data: products, isFetching, error } = useCurrentRouteData();
if (isFetching) return <div>Loading...</div>;
if (error) return <div>Error loading products</div>;
return (
<div>
<h1>Products</h1>
{products?.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
};
// Client-side navigation
ProductsPage.clientFetcher = async () => {
const res = await fetch('https://api.example.com/products');
const products = await res.json();
return products;
};
// Server-side rendering
ProductsPage.serverFetcher = async () => {
const res = await fetch('https://api.example.com/products');
const products = await res.json();
return products;
};
export default ProductsPage;
Key Migration Steps:
- Convert async components to regular React components
- Move data fetching logic from component body to separate
serverFetcher
/clientFetcher
functions - Add loading and error handling in the component using
useCurrentRouteData
- Remove 'use client' directives - not needed in Catalyst
Step 6: Data Fetching
Key Migration Steps:
Replace Next.js data fetching methods (
getServerSideProps, getStaticProps
) with Catalyst's data fetching methods:getServerSideProps
→ComponentName.serverFetcher
getStaticProps
→ComponentName.serverFetcher
(with caching logic if needed)
Update components to use the useCurrentRouteData hook:
const { isFetching, isFetched, error, data, refetch, clear } = useCurrentRouteData()
Migration Example
Next.js Implementation:
// pages/user/[id].js
export default function UserProfile({ user }) {
return <div>{user.name}</div>
}
export const getServerSideProps = async (context) => {
const { id } = context.params
const res = await fetch(`https://api.example.com/users/${id}`)
const user = await res.json()
return { props: { user } }
}
Catalyst Implementation:
// src/components/UserProfile.js
import { useCurrentRouteData } from '@tata1mg/router'
const UserProfile = () => {
const { data } = useCurrentRouteData()
return <div>{data?.name}</div>
}
UserProfile.serverFetcher = async ({ params }) => {
const { id } = params
const res = await fetch(`https://api.example.com/users/${id}`)
return await res.json()
}
UserProfile.clientFetcher = async ({ params }) => {
const { id } = params
const res = await fetch(`https://api.example.com/users/${id}`)
return await res.json()
}
export default UserProfile
Step 7: Middlewares
- In Next.js, you used middleware.ts (or .js) in the project root.
- Catalyst offers a flexible approach to defining server-side code, granting you greater control and customization over server operations.
// server/index.js
export function addMiddlewares(app) {
app.use()
}
Step 8: State Management
Migration Example: Redux Store Setup
Next.js Implementation:
// store/store.js
import { createStore } from 'redux'
import rootReducer from './reducers'
const store = createStore(rootReducer)
export default store
// pages/_app.js
import { Provider } from 'react-redux'
import store from '../store/store'
export default function App({ Component, pageProps }) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
Catalyst Implementation:
When you create a new Catalyst application using create-catalyst-app
, the following Redux setup comes pre-configured:
- Store configuration at
src/js/store/index.js
with proper initialization - Redux Provider wrapper automatically handled by the framework - no need to manually wrap your app
- Server-side store integration with access to the store during SSR via fetchers
- Initial state hydration from server to client automatically managed
// src/js/store/index.js
import { createStore } from 'redux'
import homeReducer from '@reducers/homeReducer.js'
export default function configureStore(initialState, request) {
// request object available when store is initialized on server
const store = createStore(homeReducer, initialState)
return store
}
Key Migration Steps:
Move Redux store setup from your custom location to
src/js/store/index.js
Remove Provider wrapper from your app component - Catalyst handles this automatically
Access store in fetchers using the
store
parameter:HomePage.serverFetcher = async ({ req }, { store }) => {
return store.dispatch(getHomePageData())
}If using other state management solutions: Implement at the root of your Catalyst application and update components accordingly.
Step 9: Styling
Migration Example: CSS Modules
Next.js Implementation:
// styles/Home.module.css
.container { padding: 2rem; }
// pages/index.js
import styles from '../styles/Home.module.css'
export default function Home() {
return <div className={styles.container}>Home</div>
}
Catalyst Implementation:
// src/components/Home/Home.module.css
.container { padding: 2rem; }
// src/components/Home/Home.js
import styles from './Home.module.css'
export default function Home() {
return <div className={styles.container}>Home</div>
}
Key Migration Steps:
- Move CSS files to component directories or appropriate locations in the new structure
- Global styles: Place in
/src/static/css/base
and import inclient/styles.js
- CSS Modules work out-of-the-box - no configuration needed
- SCSS support is included - just use
.scss
file extensions
📚 For detailed styling options and examples, see: Styling Documentation
Step 10: Image Handling
- Replace
next/image
usage with standard<img>
tags or create a custom image component.
Step 11: Font Optimization
Migration Example: Next.js Font to Manual Implementation
Next.js Implementation:
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
})
export default function MyApp({ Component, pageProps }) {
return (
<main className={inter.className}>
<h1 className={robotoMono.className}>Hello World</h1>
<Component {...pageProps} />
</main>
)
}
Catalyst Implementation Options:
Option 1: Manual CSS Font Loading
/* src/static/css/base/fonts.css */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto+Mono:wght@400;500&display=swap');
:root {
--font-inter: 'Inter', sans-serif;
--font-roboto-mono: 'Roboto Mono', monospace;
}
body {
font-family: var(--font-inter);
}
.font-mono {
font-family: var(--font-roboto-mono);
}
Option 2: React Helmet for Dynamic Font Management
First install React Helmet: npm install react-helmet
import { Helmet } from 'react-helmet'
const Component = ({ children }) => {
return (
<>
<Helmet>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto+Mono:wght@400;500&display=swap"
rel="stylesheet"
/>
<link
rel="preload"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
as="style"
/>
</Helmet>
{children}
</>
)
}
- React Helmet: react-helmet - Dynamic head management
Step 12: Script Management
Migration Example: Next.js Script to Manual Implementation
Next.js Implementation:
import Script from 'next/script'
export default function MyApp({ Component, pageProps }) {
return (
<>
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
`}
</Script>
<Component {...pageProps} />
</>
)
}
React Helmet for Dynamic Script Management
Install React Helmet: npm install react-helmet
// src/components/Analytics/Analytics.js
import { Helmet } from 'react-helmet'
const Analytics = ({ measurementId }) => {
return (
<Helmet>
{/* External script with preloading */}
<link
rel="preload"
href={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
as="script"
/>
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
/>
{/* Inline script */}
<script>
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${measurementId}');
`}
</script>
</Helmet>
)
}
- React Helmet: react-helmet - Dynamic head management with script support
- React Helmet Async: react-helmet-async - SSR-safe version with better performance
Step 13: Layouts
Migration Example: Global Layout
Next.js Implementation:
// components/layout.js
import Header from './Header'
import Footer from './footer'
export default function Layout({ children }) {
return (
<>
<Header />
<main>{children}</main>
<Footer />
</>
)
}
Catalyst Implementation:
// src/js/containers/App/index.js
import React from "react"
import { Outlet } from "@tata1mg/router"
import Header from './Header'
import Footer from './footer'
const App = () => {
return (
<div>
<Header />
<main>
<Outlet />
</main>
<Footer />
</div>
)
}
📚 For complete layout setup guide, see: Layout Setup Documentation
Step 14: Metadata and Head Management
Migration Example: Dynamic Metadata
Next.js Implementation:
// pages/user/[id].js
import Head from 'next/head'
export default function UserProfile({ user }) {
return (
<>
<Head>
<title>Profile</title>
</Head>
<div>User Profile</div>
</>
)
}
Catalyst Implementation:
// src/components/UserProfile/UserProfile.js
const UserProfile = () => {
return <div>User Profile</div>
}
UserProfile.setMetaData = () => {
return [
<title key="title">Profile</title>,
]
}
📚 For detailed metadata management, see: Dynamic Metadata Documentation
Step 15: Environment Variables
Migration Example: Environment Configuration
Next.js Implementation:
// .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://localhost:5432/mydb
// Usage in code
const apiUrl = process.env.NEXT_PUBLIC_API_URL
const dbUrl = process.env.DATABASE_URL
Catalyst Implementation:
// config/config.json
{
"API_URL": "https://api.example.com",
"DATABASE_URL": "postgresql://localhost:5432/mydb",
"CLIENT_ENV_KEYS": ["API_URL"]
}
// Usage in code
const apiUrl = process.env.API_URL // Available on both client & server
const dbUrl = process.env.DATABASE_URL // Server-only (not in CLIENT_ENV_KEYS)
Key Migration Steps:
- Move environment variables from
.env
files toconfig/config.json
- Remove
NEXT_PUBLIC_
prefixes - useCLIENT_ENV_KEYS
instead - List client-accessible variables in the
CLIENT_ENV_KEYS
array - Server-only variables should NOT be included in
CLIENT_ENV_KEYS
⚠️ Security Note: Variables listed in CLIENT_ENV_KEYS
are exposed to the client and can be viewed in browser dev tools.
📚 For complete environment configuration guide, see: Environment Variables Documentation
Step 16: Build and Deployment
Update your build script from next build to Catalyst's build process:
npm run build
.Modify your deployment scripts and configurations to work with the new Catalyst build output.
Step 17: Final Checks
Update all import statements to reflect the new project structure.
Replace any remaining Next.js specific components or functions with their Catalyst equivalents or standard React/HTML alternatives.
Test your application thoroughly to ensure all features work as expected in the new Catalyst environment.